package com.haust.leyou.service;


import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.haust.leyou.client.BrandClient;
import com.haust.leyou.client.CategoryClient;
import com.haust.leyou.client.GoodsClient;
import com.haust.leyou.client.SpecificationClient;
import com.haust.leyou.pojo.*;
import com.haust.leyou.repository.GoodsRepository;
import com.haust.leyou.vo.SearchRequest;
import com.haust.leyou.vo.SearchResult;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.*;

/**
 * @Auther: csp1999
 * @Date: 2020/12/13/12:08
 * @Description:
 */
@Service
public class SearchService {

    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private BrandClient brandClient;

    @Autowired
    private GoodsClient goodsClient;

    @Autowired
    private SpecificationClient specificationClient;

    @Autowired
    private GoodsRepository goodsRepository;

    // JSON 工具
    private static final ObjectMapper MAPPER = new ObjectMapper();

    /**
     * 根据是spu 构建 Goods 对象
     *
     * @param spu
     * @return
     */
//    public Goods buildGoods(SpuBo spuBo) throws IOException {
//        Goods goods = new Goods();
//
//        //1.查询商品分类名称
//        List<String> names = this.categoryClient.queryNamesByIds(Arrays.asList(spuBo.getCid1(),spuBo.getCid2(),spuBo.getCid3()));
//        //2.查询sku
//        List<Sku> skus = this.goodsClient.querySkusBySpuId(spuBo.getId());
//        //3.查询详情
//        SpuDetail spuDetail = this.goodsClient.querySpuDetailBySpuId(spuBo.getId());
//
//        //4.处理sku,仅封装id，价格、标题、图片、并获得价格集合
//        List<Long> prices = new ArrayList<>();
//        List<Map<String,Object>> skuLists = new ArrayList<>();
//        skus.forEach(sku -> {
//            prices.add(sku.getPrice());
//            Map<String,Object> skuMap = new HashMap<>();
//            skuMap.put("id",sku.getId());
//            skuMap.put("title",sku.getTitle());
//            skuMap.put("price",sku.getPrice());
//            //取第一张图片
//            skuMap.put("image", StringUtils.isBlank(sku.getImages()) ? "" : StringUtils.split(sku.getImages(),",")[0]);
//            skuLists.add(skuMap);
//        });
//
//        // 提取公共属性
//        List<Map<String,Object>> genericSpecs = MAPPER.readValue(spuDetail.getGenericSpec(),new TypeReference<List<Map<String,Object>>>(){});
//        // 提取特有属性
//        Map<String,Object> specialSpecs = MAPPER.readValue(spuDetail.getSpecialSpec(),new TypeReference<Map<String,Object>>(){});
//
//        //过滤规格模板，把所有可搜索的信息保存到Map中
//        Map<String,Object> specMap = new HashMap<>();
//
//        String searchable = "searchable";
//        String v = "v";
//        String k = "k";
//        String options = "options";
//
//        genericSpecs.forEach(m -> {
//            List<Map<String, Object>> params = (List<Map<String, Object>>) m.get("params");
//            params.forEach(spe ->{
//                if ((boolean)spe.get(searchable)){
//                    if (spe.get(v) != null){
//                        specMap.put(spe.get(k).toString(), spe.get(v));
//                    }else if (spe.get(options) != null){
//                        specMap.put(spe.get(k).toString(), spe.get(options));
//                    }
//                }
//            });
//        });
//        goods.setId(spuBo.getId());
//        goods.setSubTitle(spuBo.getSubTitle());
//        goods.setBrandId(spuBo.getBrandId());
//        goods.setCid1(spuBo.getCid1());
//        goods.setCid2(spuBo.getCid2());
//        goods.setCid3(spuBo.getCid3());
//        goods.setCreateTime(spuBo.getCreateTime());
//        goods.setAll(spuBo.getTitle() + " " + StringUtils.join(names, " "));
//        goods.setPrice(prices);
//        goods.setSkus(MAPPER.writeValueAsString(skuLists));
//        goods.setSpecs(specMap);
//        return goods;
//    }
    public Goods buildGoods(Spu spu) throws IOException {
        Goods goods = new Goods();

        // 根据分类id 查询分类名称集合
        List<String> categoryNameList = categoryClient.queryNamesByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));

        // 根据品牌id 查询品牌
        Brand brand = brandClient.queryBrandById(spu.getBrandId());

        // 根据spu 中的cid3 查询出所有的搜索规格参数
        List<SpecParam> specParamList = specificationClient.queryParams(spu.getCid3(), null, null, true);

        // 根据spuId 查询 spuDetail
        SpuDetail spuDetail = goodsClient.querySpuDetailBySpuId(spu.getId());
        // 把商品的通用的格参数genericSpec值进行反序列化，使其String 类型转换成Map<String, Object> 类型
        Map<String, Object> genericSpecMap = MAPPER.readValue(spuDetail.getGenericSpec(), new TypeReference<Map<String, Object>>() {
        });
        // 把商品特殊规格参数specialSpec值进行反序列化，使其String 类型转换成Map<String, List<Object>> 类型
        Map<String, List<Object>> specialSpecMap = MAPPER.readValue(spuDetail.getSpecialSpec(), new TypeReference<Map<String, List<Object>>>() {
        });

        Map<String, Object> specs = new HashMap<>();
        specParamList.forEach(specParam -> {
            // 判断规格参数的类型，是否是通用的规格参数
            if (specParam.getGeneric()) {
                // 如果是通用类型的参数，则从genericSpecMap中获取规格参数值：
                String value = genericSpecMap.get(specParam.getId().toString()).toString();
                // 判断是否是数值类型参数，true 或 false
                if (specParam.getNumeric()) {
                    // 如果是数值类型的参数,则根据数值选取一个具体的数值区间，并返回给value
                    value = chooseSegment(value, specParam);
                }
                // 存入specs
                specs.put(specParam.getName(), value);
            } else {
                // 如果是特殊规格类型的参数，则从specialSpecMap中获取规格参数值：
                List<Object> valueList = specialSpecMap.get(specParam.getId().toString());
                // 踩坑：https://blog.csdn.net/u010553867/article/details/104452764
                // specialSpecMap.get(specParam.getId().toString());
                // 存入specs
                specs.put(specParam.getName(), valueList);
            }
        });

        // 根据spuId 查询所有的sku
        List<Sku> skuList = goodsClient.querySkusBySpuId(spu.getId());

        // 初始化价格的list集合，收集所有sku价格
        ArrayList<Long> priceList = new ArrayList<>();
        // 收集sku 必要字段信息的list集合
        List<Map<String, Object>> skuMapList = new ArrayList<>();
        skuList.forEach(sku -> {
            priceList.add(sku.getPrice());

            Map<String, Object> map = new HashMap<>();
            map.put("id", sku.getId());
            map.put("title", sku.getTitle());
            map.put("price", sku.getPrice());
            // 获取sku 中的图片，数据库的图片可能是多张，多张图片是以“，”分隔，
            // 所以这里我们也以逗号切割，返回图片数组，并获取第一张图片即可
            map.put("image", StringUtils.isBlank(sku.getImages())
                    ? ""
                    : StringUtils.split(sku.getImages(), ",")[0]);

            skuMapList.add(map);
        });

        goods.setId(spu.getId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setBrandId(spu.getBrandId());
        goods.setCreateTime(spu.getCreateTime());
        goods.setSubTitle(spu.getSubTitle());
        // 拼接all 字段，需要分类名称以及品牌名称
        // eg:
        goods.setAll(spu.getTitle() + " " + StringUtils.join(categoryNameList, " ") + " " + brand.getName());
        System.out.println(goods.getAll());
        // 获取spu 下的所有sku 价格
        goods.setPrice(priceList);
        // 获取spu 下的所有sku的list集合 并转换成json 字符串
        goods.setSkus(MAPPER.writeValueAsString(skuMapList));
        // 获取所有查询的规格参数，eg: {name:value}
        goods.setSpecs(specs);

        return goods;
    }

    // 选取具体的数值区间的方法
    private String chooseSegment(String value, SpecParam p) {
        double val = NumberUtils.toDouble(value);
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    /**
     * 商品搜索
     *
     * @param searchRequest
     * @return
     */
    public SearchResult search(SearchRequest searchRequest) {

        // 判断查询条件是否为空
        if (StringUtils.isBlank(searchRequest.getKey())) {
            // 返回默认结果集
            return null;
        }

        // 初始化自定义查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 添加分页条件，页码从0开始
        queryBuilder.withPageable(PageRequest.of(searchRequest.getPage() - 1, searchRequest.getSize()));
        // 添加查询条件
        // QueryBuilder basicQuery = QueryBuilders.matchQuery("all", searchRequest.getKey()).operator(Operator.AND);
        BoolQueryBuilder basicQuery = buildBoolQueryBuilder(searchRequest);

        queryBuilder.withQuery(basicQuery);
        // 添加结果集过滤，只需要：id,subTitle, skus
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, null));

        String categoryAggName = "categories";
        String brandAggName = "brands";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));

        // 执行搜索，获取搜索的结果集
        AggregatedPage<Goods> goodsPage = (AggregatedPage<Goods>) this.goodsRepository.search(queryBuilder.build());

        // 解析聚合结果集
        List<Map<String, Object>> categories = getCategoryAggResult(goodsPage.getAggregation(categoryAggName));
        List<Brand> brands = getBrandAggResult(goodsPage.getAggregation(brandAggName));

        System.out.println("==========getCategoryAggResult和getBrandAggResult通过============");
        // 判断是否是一个分类，当只有一个分类时，才做规格参数的聚合
        List<Map<String, Object>> specs = null;
        if (!CollectionUtils.isEmpty(categories) && categories.size() == 1) {
            // 对规格参数进行聚合
            specs = getParamAggResult((Long) categories.get(0).get("id"), basicQuery);
        }

        // 封装成需要的返回结果集
        return new SearchResult(goodsPage.getTotalElements(), goodsPage.getTotalPages(), goodsPage.getContent(), categories, brands, specs);
    }

    // 构建bool 查询
    private BoolQueryBuilder buildBoolQueryBuilder(SearchRequest searchRequest) {

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 给布尔查询添加基本查询条件
        boolQueryBuilder.must(QueryBuilders.matchQuery("all", searchRequest.getKey()).operator(Operator.AND));
        // 添加过滤条件
        // 获取用户选择的过滤信息
        Map<String, String> filter = searchRequest.getFilter();
        for (Map.Entry<String, String> entry : filter.entrySet()) {
            String key = entry.getKey();
            if (StringUtils.equals("品牌", key)) {
                key = "brandId";
            } else if (StringUtils.equals("分类", key)) {
                key = "cid3";
            } else {// key是规格参数
                key = "specs." + key + ".keyword";
            }
            boolQueryBuilder.filter(QueryBuilders.termQuery(key, entry.getValue()));
        }

        return boolQueryBuilder;
    }

    private List<Map<String, Object>> getParamAggResult(Long cid, QueryBuilder basicQuery) {

        // 自定义查询对象构建
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 添加基本查询条件
        queryBuilder.withQuery(basicQuery);

        // 查询要聚合的规格参数
        List<SpecParam> params = specificationClient.queryParams(cid, null, null, true);

        // 添加规格参数的聚合
        params.forEach(param -> {
            // 每一个规格参数都添加聚合
            queryBuilder.addAggregation(AggregationBuilders.terms(param.getName()).field("specs." + param.getName() + ".keyword"));
        });

        // 添加结果集过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{}, null));

        // 执行聚合查询,获取聚合结果集
        AggregatedPage<Goods> goodsPage = (AggregatedPage<Goods>) goodsRepository.search(queryBuilder.build());

        List<Map<String, Object>> specs = new ArrayList<>();
        // 解析聚合结果集:key-聚合名称(规格参数名称) value-聚合对象
        Map<String, Aggregation> aggregationMap = goodsPage.getAggregations().asMap();
        // 遍历aggregationMap 并转换成List<Map<String, Object>>类型返回
        for (Map.Entry<String, Aggregation> entry : aggregationMap.entrySet()) {
            // 初始化一个map {k:规格参数名称,options:聚合的规格参数值}
            Map<String, Object> map = new HashMap<>();
            map.put("k", entry.getKey());

            // 初始化一个options 集合，收集桶中的key
            List<String> options = new ArrayList<>();
            // 获取聚合
            System.out.println(entry.getValue().getName());
            StringTerms terms = (StringTerms) entry.getValue();
            // 获取桶集合
            terms.getBuckets().forEach(bucket -> {
                options.add(bucket.getKeyAsString());
            });
            map.put("options", options);
            specs.add(map);
        }
        return specs;
    }

    // 解析Brand聚合的结果集：解析Brand
    private List<Brand> getBrandAggResult(Aggregation aggregation) {
        // 处理聚合结果集
        LongTerms terms = (LongTerms) aggregation;
        // 获取所有的品牌id桶
        List<LongTerms.Bucket> buckets = terms.getBuckets();
        // 定义一个品牌集合，搜集所有的品牌对象
        List<Brand> brands = new ArrayList<>();
        // 解析所有的id桶，查询品牌
        buckets.forEach(bucket -> {
            Brand brand = this.brandClient.queryBrandById(bucket.getKeyAsNumber().longValue());
            brands.add(brand);
        });
        return brands;
        // 解析聚合结果集中的桶，把桶的集合转化成id的集合
        // List<Long> brandIds = terms.getBuckets().stream().map(bucket -> bucket.getKeyAsNumber().longValue()).collect(Collectors.toList());
        // 根据ids查询品牌
        //return brandIds.stream().map(id -> this.brandClient.queryBrandById(id)).collect(Collectors.toList());
        // return terms.getBuckets().stream().map(bucket -> this.brandClient.queryBrandById(bucket.getKeyAsNumber().longValue())).collect(Collectors.toList());
    }

    // 解析Category聚合的结果集：解析Category
//    private List<Map<String, Object>> getCategoryAggResult(Aggregation aggregation) {
//        LongTerms terms = (LongTerms) aggregation;
//
//        // 获取聚合中的桶的集合，并转换成List<Map<String, Object>> 类型
//        return terms.getBuckets().stream().map(bucket -> {
//            // 初始一个map
//            Map<String, Object> map = new HashMap<>();
//            // 获取桶中的分类id(key)
//            long id = bucket.getKeyAsNumber().longValue();
//            // 根据分类id查询分类名称
//            List<String> names = categoryClient.queryNamesByIds(Arrays.asList(id));
//            map.put("id", id);
//            map.put("name", names.get(0));
//
//            return map;
//        }).collect(Collectors.toList());
//    }
    // 解析Category聚合的结果集：解析Category
    private List<Map<String, Object>> getCategoryAggResult(Aggregation aggregation) {
        // 处理聚合结果集
//        LongRareTerms terms = (LongRareTerms) aggregation;
        LongTerms terms = (LongTerms) aggregation;
//        StringTerms terms = (StringTerms) aggregation;
        // 获取所有的分类id桶
        List<LongTerms.Bucket> buckets = terms.getBuckets();
        // 定义一个品牌集合，搜集所有的品牌对象
        List<Map<String, Object>> categories = new ArrayList<>();
        List<Long> cids = new ArrayList<>();
        // 解析所有的id桶，查询品牌
        buckets.forEach(bucket -> {
            cids.add(bucket.getKeyAsNumber().longValue());
        });
        List<String> names = this.categoryClient.queryNamesByIds(cids);
        for (int i = 0; i < cids.size(); i++) {
            Map<String, Object> map = new HashMap<>();
            map.put("id", cids.get(i));
            map.put("name", names.get(i));
            categories.add(map);
        }
        return categories;
    }

    /**
     * 更新/保存 索引库
     *
     * @param id
     */
    public void save(Long id) throws IOException {

        Spu spu = goodsClient.querySpuById(id);

        Goods goods = buildGoods(spu);

        goodsRepository.save(goods);
    }

    /**
     * 删除 索引库
     *
     * @param id
     */
    public void delete(Long id) {
        goodsRepository.deleteById(id);
    }
}
